package org.dslmagic.util;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.dslmagic.dsl.Dsl;
import org.dslmagic.interpreter.DslContext;

import com.sun.xml.internal.ws.client.sei.MethodHandler;

public final class Code {

	public static Map<String, Dsl> implementations(Class<?> cinterface,
			String[] methodNames, Dsl[] dsls) {
		if (methodNames == null) {
			Method[] methods = cinterface.getDeclaredMethods();
			if (methods.length != 1) {
				throw new IllegalArgumentException(
						"The interface "
								+ cinterface
								+ "must have exactly 1 method if no method names are specified!");
			}
			methodNames = new String[] { methods[0].getName() };
		}
		Map<String, Dsl> implementations = new HashMap<String, Dsl>();
		for (int i = 0; i < methodNames.length; i++) {
			implementations.put(methodNames[i], dsls[i]);
		}
		return implementations;
	}

	public static <T, TO> Object define(final TO object,
			final Class<T> cinterface, final Map<String, Dsl> implementations) {
		try {
			ProxyFactory factory = new ProxyFactory();
			Class<?> superclass;

			if (object != null) {
				superclass = object.getClass();
			} else {
				superclass = Object.class;
			}

			boolean isProxy = ProxyFactory.isProxyClass(superclass);
			if (isProxy) {
				superclass = superclass.getSuperclass();
			}

			factory.setSuperclass(superclass);

			if (cinterface != null) {
				factory.setInterfaces(new Class[] { cinterface });
			}

			MethodHandler handler = new MethodHandler() {
				@Override
				public Object invoke(Object self, Method thisMethod,
						Method proceed, Object[] args) throws Throwable {
					String methodName = thisMethod.getName();
					Dsl dsl = implementations.get(methodName);
					Class<?> clazz = thisMethod.getDeclaringClass();
					if ((cinterface != null && clazz.equals(cinterface) && dsl != null)
							|| (cinterface == null && dsl != null)) {
						DslContext context = Dsl.context().set("$", self)
								.set("$args", args);
						return dsl.run(context);
					} else {
						return thisMethod.invoke(object, args);
					}
				}
			};
			return factory.create(new Class[] {}, new Object[] {}, handler);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
